from django.forms import ModelForm
from pprint import pprint
from django.core import serializers
from django_sproutcore import signals
   
class ScModel(ModelForm):
    """
    The adapter class for all django models. For a linking between your 
    sproutcore and django models inherit from this class and specify the 
    django model and the exclude parameter. 
    
    Example:
    my_app/models.py
    class MyClass(models.Model):
        name = models.CharField(max_length=255)
        notes = models.TextField(null=True, blank=True)
        user = models.ForeignKey(User, related_name='users', null=True, blank=True)
    
    my_sc_models.py
    
    class MyClassSC(ScModel):
        class Meta:
            model = models.MyClass
            exclude = ('notes')
    """
    def __init__(self, *args, **kwargs):
        self.perm_user = kwargs.pop('user', None)
        signals.sc_model_init.send(sender=self, user=self.perm_user, args=args, kwargs=kwargs)
        #print "ScModel.__init__: user is %s" % self.perm_user
        #print "        data: %s" % kwargs['data']
        super(ScModel, self).__init__(*args, **kwargs)
    
    def save(self, commit=True):
        instance = super(ScModel, self).save(commit=False)
        signals.sc_model_instance_pre_save.send(sender=self, user=self.perm_user, model_instance=instance)
        if commit==True:
            instance.save()
            signals.sc_model_instance_post_save.send(sender=self, user=self.perm_user, model_instance=instance)
        return instance
    
    def delete(self):
        signals.sc_model_instance_pre_delete.send(sender=self, user=self.perm_user, model_instance=self.instance)
        self.instance.delete()
        
    def as_dict(self):
        """
        Retuns a new dict from the ScModel. The type specify the class name 
        of a django model, the id his primary key.
        The dict represents the original data from the database. once .save() has been
        called successfully it represents the new data.
        
        Example:
        {'user': None, 'notes': u'This is my Class', 'type': 'MyClass', 'id': 1, 'name': u'myclass'}
        """
        signals.sc_model_instance_pre_show.send(sender=self, model_instance=self.instance, user=self.perm_user)
        # TODO: should only return fields marked as readable by permissions
        response = {'type': self.__class__.__name__,'id': self.instance.id}
        # TODO: handle related fields correctly
        # TODO: handle validation errors somehow
        obj_dict = serializers.serialize('python', [self.instance])[0]
        for name, field in self.fields.items():
            # this uses the ModelField value_to_string method to convert to a string. This method is usually
            # in charge of serializing the data to a string (json, xml). ideal! :-)
            # TODO: test date, datetime, decimal conversion. Is the format Sproutcore compatible?
            #response[name] = self.instance._meta.get_field(name).value_to_string(self.instance)
            value = obj_dict['fields'][name]
            if value==None:
                response[name] = None
            elif type(value)==type(23):
                response[name] = value
            elif type(value)==type(True):
                response[name] = value
            else:
                response[name] = self.instance._meta.get_field(name).value_to_string(self.instance)
        return response
         
    
    
class ScModelManager(object):
    """
    The adapter class of all django managers. The ScModelManager gives you the
    standard operations of the sproutcore framework. To implement additional
    or overwrite the standard operations inherit form this class and define 
    your own operations.
    
    Example:
    class MyManager(ScModelManager):
        def my_operation(self, parameters):
            return parameters
    """
    check_django_permissions = True
    check_advanced_permissions = True
    
    def __init__(self, sc_model_class, registry=None):
        self.ScModel = sc_model_class
        self.Model = sc_model_class._meta.model # Django Model
        self.registry = registry
    
    def create(self, data, user):
        signals.sc_model_pre_create.send(sender=self, model=self.Model, user=user)
        rawOutput = []
        #errors = []
        for row in data:
            # A ScModel that is newly created must not contain a id property
            row.pop('id', None)
            # fetch the temporary id of this object for later use
            frontend_guid = row.pop(u'_guid', None)
            
            sc_model = self.ScModel(row, user=user)
            if sc_model.is_valid():
                obj = sc_model.save()
                new_dict = sc_model.as_dict()
                # add the _guid back so sproutcore can update its db with the real id
                new_dict['_guid'] = frontend_guid
            else:
                # there were validation errors
                pass
            if not bool(sc_model.errors):
                rawOutput.append(new_dict)
            else:
                # do nothing for now... 
                # TODO: the sproutcore server must be modified to allow handling of errors
                #errors.append({'id': frontend_guid, 'errors': errors})
                pass
        signals.sc_model_post_create.send(sender=self, model=self.Model, user=user)
        return rawOutput   
    
            
    def update(self, data, user):
        signals.sc_model_pre_update.send(sender=self, model=self.Model, user=user)
        rawOutput = []
        errors = []
        # reorder data into a dict with the 'id' attribute as key
        id_ordered_data = {}
        for row in data:
            id_ordered_data[row['id']] = row
        # get the data from the database
        objs = self.Model.objects.filter(id__in=id_ordered_data.keys())
        for obj in objs:
            #auth
            sc_model = self.ScModel(data=id_ordered_data[obj.id],instance=obj, user=user)
            if sc_model.is_valid():
                sc_model.save()
            else:
                # there were validation errors
                pass
            if not bool(sc_model.errors):
                rawOutput.append( sc_model.as_dict() )
            else:
                # do nothing for now... 
                # TODO: the sproutcore server must be modified to allow handling of errors
                #errors.append({'id': frontend_guid, 'errors': errors})
                pass
        signals.sc_model_post_update.send(sender=self, model=self.Model, user=user)
        return rawOutput
    
    def destroy(self, ids, user):
        signals.sc_model_pre_delete.send(sender=self, model=self.Model, user=user)
        rawOutput = []
        ids_int = []
        for id in ids:
            ids_int.append(int(id))
        objs = self.Model.objects.filter(id__in=ids_int)
        for obj in objs:
            #auth
            sc_model = self.ScModel(data={},instance=obj, user=user)
            sc_model.delete()
            
        signals.sc_model_post_delete.send(sender=self, model=self.Model, user=user)
        return True
    
    def show(self, ids, user):
        signals.sc_model_pre_show.send(sender=self, model=self.Model, user=user)
        objs = self.Model.objects.filter(id__in=ids)
        rawOutput = []
        for obj in objs:
            rawOutput.append(self.ScModel(instance=obj, user=user).as_dict() )
        signals.sc_model_post_show.send(sender=self, model=self.Model, user=user)
        return rawOutput
          
    def list(self, query_parameters, user):
        signals.sc_model_pre_list.send(sender=self, model=self.Model, user=user)
        objs = self.Model.objects.all()
        # handle SproutCore supported listFor conditions
        query_parameters = dict(query_parameters)
        
        ordering = query_parameters.pop('order', '').split(',')
        limit = query_parameters.pop('limit', None)
        offset = query_parameters.pop('offset', None)
        
        # the sequence of commands is important. 
        #ordering and filtering must be done
        # before any slicing
        
        # ordering
        if '' in ordering:
            # could happen if ordering was an empty string or
            # begins or ends with a ','
            ordering.remove('')
        objs = objs.order_by(*ordering)
                
        # custom filtering
        for key,value in query_parameters.items():
            params = {str(key):str(value)}
            print params
            objs = objs.filter(**params)
        
        # offset and limit
        if offset:
            objs=objs[int(offset):]
        if limit:
            objs=objs[:int(limit)]

        rawOutput = []
        id_list = []
        for obj in objs:
            sc_model = self.ScModel(instance=obj, user=user)
            rawOutput.append( sc_model.as_dict() )
            id_list.append(obj.pk)
        signals.sc_model_post_show.send(sender=self, model=self.Model, user=user)
        return {'records': rawOutput, 'ids': id_list}
    